/** @file b_renderer.cpp
	@brief Geometry batch renderer */

#include "render/batch/b_renderer.h"

//==============================================================================
// Global variables
//==============================================================================
G3::BATCH_RENDERER G3::MainBatchRenderer;

//==============================================================================
// Constructors
//==============================================================================
G3::BATCH_RENDERER::BATCH_RENDERER () {
    // Attempt to use VBO's by default
    optimization = O_VBO;
    // VBO's
    vbo = NULL;
    last_vbo = NULL;
    // DisplayLists
    dlist = NULL;
    last_dlist = NULL;
    // Geometry pointers
    gptr = NULL;
    last_gptr = NULL;

    frustum = NULL;

    last_material = -1;

    batches_rendered = 0;

    log = &G3::MainLog;
}

//==============================================================================
// Destructors
//==============================================================================
G3::BATCH_RENDERER::~BATCH_RENDERER () {
    if (!spatial_objs.empty ())
        spatial_objs.clear ();
}

//==============================================================================
// Performs the rendering
//==============================================================================
void G3::BATCH_RENDERER::render () {
    int error = 0;
    bool rebuild_dls = false;

    fps_counter.CheckTime ();

    // Render the old batchlist if the new one isn't sorted yet
    if (!MainBatchSorter.batches.empty ()) {
        // Render batches
        //! \note VBO swaps are the worst state changes
        //! \todo VBO update
        //! \todo It's not good to turn the VBO on & off each frame - things would get a lot faster if the GUI could use batches aswell
        switch (optimization) {
            // Use geometry pointers
            case O_NONE:
                if (gptr != last_gptr)
                    last_gptr = gptr;

                // Avoid possible SIGSEV's (using NULL pointers)
                if (last_gptr == NULL)
                    return;

                break;
            // Use displaylists
            case O_DISPLAYLISTS:
                // The DisplayLists need to be rebuilt if the DisplayList Object has changed
                if (dlist != last_dlist) {
                    rebuild_dls = true;
                    last_dlist = dlist;
                } else
                    rebuild_dls = false;

                // Avoid possible SIGSEV's (using NULL pointers)
                if (last_dlist == NULL)
                    return;

                break;
            // Use VBO-s
            case O_VBO:
                if (last_vbo != vbo) {
                    // Unbind the last VBO
                    if (last_vbo != NULL)
                        last_vbo->unbind ();
                    // Bind the new one
                    if (vbo != NULL)
                        vbo->bind ();

                    last_vbo = vbo;
                }

                // Avoid possible SIGSEV's (using NULL pointers)
                if (last_vbo == NULL)
                    return;

                break;
        }

        batches_rendered = 0;

        MainBatchSorter.mutex_batches.lock ();

        // Go through the sorted batches
        batch_iter = MainBatchSorter.batches.begin ();
        while (batch_iter != MainBatchSorter.batches.end ()) {
            // Ignore NULL pointers
            if (*batch_iter == NULL) {
                printf ("NULL batchpointer..\n");
                batch_iter++;
                continue;
            }

            // Material state change
            if (last_material != (*batch_iter)->matID) {
                switch (optimization) {
                    case O_NONE:
                        // Unbind the last material
                        if (last_material >= 0)
                            last_gptr->unbind_material ();

                        // Bind the new one
                        last_gptr->bind_material ((*batch_iter)->matID);
                        break;

                    case O_DISPLAYLISTS:
                        // Unbind the last material
                        if (last_material >= 0)
                            last_dlist->unbind_material ();

                        // Bind the new one
                        last_dlist->bind_material ((*batch_iter)->matID);
                        break;

                    case O_VBO:
                        // Unbind the last material
                        if (last_material >= 0)
                            last_vbo->unbind_material ();

                        // Bind the new one
                        last_vbo->bind_material ((*batch_iter)->matID);
                        break;
                }

                // Cache the last material
                last_material = (*batch_iter)->matID;
            }

            // Rebuild the DisplayLists before rendering (if needed)
            if (optimization == O_DISPLAYLISTS && (rebuild_dls || (*batch_iter)->display_list_id == -1)) {
                printf ("Rebuilding DisplayList for batch 0x%X..\n", *batch_iter);

                if (dlist)
                    dlist->build (*batch_iter);

                printf (">> DisplayList %d\n", (*batch_iter)->display_list_id);
            }

            // Render the batch
            if (optimization == O_NONE) {
                (*batch_iter)->render (last_gptr);
            } else {
                (*batch_iter)->render ();
            }

            // And take the next batch
            batch_iter++;
            batches_rendered++;
        }

        MainBatchSorter.mutex_batches.unlock ();

        // Unbind the last material
        switch (optimization) {
            case O_NONE:
                // Unbind the last material
                if (last_material >= 0)
                    last_gptr->unbind_material ();
                break;

            case O_DISPLAYLISTS:
                // Unbind the last material
                if (last_material >= 0)
                    last_dlist->unbind_material ();
                break;

            case O_VBO:
                // Unbind the last material
                if (last_material >= 0)
                    last_vbo->unbind_material ();
                break;
        }

        // Unbind the last VBO
//        if (optimization == G3::BATCH_RENDERER::O_VBO)
//            last_vbo->unbind ();

        error = glGetError ();
        if (error != GL_NO_ERROR) {
            if (log)
                log->Report ("BATCH_RENDERER::render: OpenGL threw error %d (%s) during the rendering\n", error, gluErrorString (error));
        }
    }

    // The DisplayLists have been rebuilt
    rebuild_dls = false;

    //printf ("Updating spatials..\n");

    // Update the batches
    spatial_iter = spatial_objs.begin ();
    while (spatial_iter != spatial_objs.end ()) {
        (*spatial_iter)->update (frustum);

        spatial_iter++;
    }

    fps_counter.AddFrame ();
}

//==============================================================================
// Attaches a spatial partitioning system to the renderer
//==============================================================================
void G3::BATCH_RENDERER::attach (const boost::shared_ptr<SPATIAL_RENDERABLE> &spatial) {
    if (spatial != NULL)
        spatial_objs.insert (spatial);
}

//==============================================================================
// Initializes the renderer with a new VBO and a frustum
//==============================================================================
bool G3::BATCH_RENDERER::init (G3::VBO *vb, G3::FRUSTUM *frust, G3::DISPLAYLIST *dl, G3::GEOM_POINTER *gp) {
    vbo = vb;
    dlist = dl;
    gptr = gp;

    frustum = frust;

    fps_counter.Start ();

    return true;
}
